#include "MiscUtil.h"
// #include <QtWin>
#include <QApplication>
#include <QDir>
#include <QMessageBox>
#include <thread>

#include "BrowseDialog.h"
#include "ComboBoxDialog.h"
#include "LineEditDialog.h"
#include "StringUtil.h"

void SetApplicationIcon(WId winId) {
  std::thread([winId] {
    HICON hIcon = LoadIcon(GetModuleHandleW(0), MAKEINTRESOURCE(100));
    SendMessageW((HWND)winId, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
    DestroyIcon(hIcon);
  }).detach();
}

QByteArray& ByteReverse(QByteArray& array) {
  int length = array.length();
  for (int i = 0; i < length / 2; i++) {
    char temp = array[i];
    array[i] = array[length - i - 1];
    array[length - i - 1] = temp;
  }
  return array;
}

QByteArray ByteReverse(QByteArray&& array) {
  int length = array.length();
  for (int i = 0; i < length / 2; i++) {
    char temp = array[i];
    array[i] = array[length - i - 1];
    array[length - i - 1] = temp;
  }
  return array;
}

bool SimpleInputBox(QWidget* parent, const QString& title, QString defaultValue,
                    QString& output, const QString& placeholderText,
                    const QIcon* icon) {
  LineEditDialog mEdit(parent);
  mEdit.setWindowIcon(icon ? *icon : parent->windowIcon());
  mEdit.setText(defaultValue);
  mEdit.setPlaceholderText(placeholderText);
  mEdit.setWindowTitle(title);
  mEdit.setCheckBox(false);
  if (mEdit.exec() == QDialog::Accepted) {
    output = mEdit.editText;
    return true;
  } else
    return false;
}

bool SimpleChoiceBox(QWidget* parent, const QString& title,
                     QString defaultValue, const QStringList& choices,
                     QString& output, bool editable,
                     const QString& placeholderText, const QIcon* icon,
                     int minimumContentsLength) {
  ComboBoxDialog mChoice(parent);
  mChoice.setWindowIcon(icon ? *icon : parent->windowIcon());
  mChoice.setEditable(editable);
  mChoice.setItems(choices);
  mChoice.setText(defaultValue);
  mChoice.setPlaceholderText(placeholderText);
  mChoice.setWindowTitle(title);
  mChoice.setCheckBox(false);
  if (minimumContentsLength >= 0)
    mChoice.setMinimumContentsLength(minimumContentsLength);
  if (mChoice.exec() == QDialog::Accepted) {
    output = mChoice.currentText();
    return true;
  } else
    return false;
}

void SimpleErrorBox(QWidget* parent, const QString& title,
                    const QString& text) {
  QMessageBox msg(QMessageBox::Critical, title, text, QMessageBox::NoButton,
                  parent);
  msg.setWindowIcon(DIcon("fatal-error.png"));
  msg.setParent(parent, Qt::Dialog);
  msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
  msg.exec();
}

void SimpleWarningBox(QWidget* parent, const QString& title,
                      const QString& text) {
  QMessageBox msg(QMessageBox::Warning, title, text, QMessageBox::NoButton,
                  parent);
  msg.setWindowIcon(DIcon("exclamation.png"));
  msg.setParent(parent, Qt::Dialog);
  msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
  msg.exec();
}

void SimpleInfoBox(QWidget* parent, const QString& title, const QString& text) {
  QMessageBox msg(QMessageBox::Information, title, text, QMessageBox::NoButton,
                  parent);
  msg.setWindowIcon(DIcon("information.png"));
  msg.setParent(parent, Qt::Dialog);
  msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
  msg.exec();
}

QString getSymbolicName(duint addr) {
  char labelText[MAX_LABEL_SIZE] = "";
  char moduleText[MAX_MODULE_SIZE] = "";
  bool bHasLabel = DbgGetLabelAt(addr, SEG_DEFAULT, labelText);
  bool bHasModule = (DbgGetModuleAt(addr, moduleText) &&
                     !QString(labelText).startsWith("JMP.&"));
  QString addrText = ToPtrString(addr);

  if (bHasLabel && bHasModule)  // <module.label>
    return QString("%1 <%2.%3>").arg(addrText).arg(moduleText).arg(labelText);
  else if (bHasModule)  // module.addr
    return QString("%1.%2").arg(moduleText).arg(addrText);
  else if (bHasLabel)  // <label>
    return QString("<%1>").arg(labelText);
  else
    return addrText;
}

QString getSymbolicNameStr(duint addr) {
  char labelText[MAX_LABEL_SIZE] = "";
  char moduleText[MAX_MODULE_SIZE] = "";
  char string[MAX_STRING_SIZE] = "";
  bool bHasString = DbgGetStringAt(addr, string);
  bool bHasLabel = DbgGetLabelAt(addr, SEG_DEFAULT, labelText);
  bool bHasModule = (DbgGetModuleAt(addr, moduleText) &&
                     !QString(labelText).startsWith("JMP.&"));
  QString addrText =
      DbgMemIsValidReadPtr(addr) ? ToPtrString(addr) : ToHexString(addr);
  QString finalText;
  if (bHasString)
    finalText = addrText + " " + QString(string);
  else if (bHasLabel && bHasModule)  //<module.label>
    finalText = QString("<%1.%2>").arg(moduleText).arg(labelText);
  else if (bHasModule)  // module.addr
    finalText = QString("%1.%2").arg(moduleText).arg(addrText);
  else if (bHasLabel)  //<label>
    finalText = QString("<%1>").arg(labelText);
  else {
    finalText = addrText;
    if (addr == (addr & 0xFF)) {
      QChar c = QChar((char)addr);
      if (c.isPrint() || c.isSpace())
        finalText += QString(" '%1'").arg(EscapeCh(c));
    } else if (addr == (addr & 0xFFF))  // UNICODE?
    {
      QChar c = QChar((ushort)addr);
      if (c.isPrint() || c.isSpace())
        finalText += QString(" L'%1'").arg(EscapeCh(c));
    }
  }
  return finalText;
}

QIcon getFileIcon(QString file) {
  SHFILEINFO info;
  if (SHGetFileInfoW((const wchar_t*)file.utf16(), 0, &info, sizeof(info),
                     SHGFI_ICON) == 0)
    return QIcon();  // API error
  QIcon result;      // = QIcon(QtWin::fromHICON(info.hIcon));
  DestroyIcon(info.hIcon);
  return result;
}

bool ExportCSV(dsint rows, dsint columns, std::vector<QString> headers,
               std::function<QString(dsint, dsint)> getCellContent) {
  BrowseDialog browse(
      nullptr,
      QApplication::translate("ExportCSV", "Export data in CSV format"),
      QApplication::translate("ExportCSV", "Enter the CSV file name to export"),
      QApplication::translate("ExportCSV",
                              "CSV files (*.csv);;All files (*.*)"),
      QApplication::applicationDirPath() + QDir::separator() + "db", true);
  browse.setWindowIcon(DIcon("database-export.png"));
  if (browse.exec() == QDialog::Accepted) {
    FILE* csv;
    bool utf16;
    csv = _wfopen(browse.path.toStdWString().c_str(), L"wb");
    if (csv == NULL) {
      GuiAddLogMessage(
          QApplication::translate("ExportCSV", "CSV export error\n")
              .toUtf8()
              .constData());
      return false;
    } else {
      duint setting;
      if (BridgeSettingGetUint("Misc", "Utf16LogRedirect", &setting))
        utf16 = !!setting;
      else
        utf16 = false;
      if (utf16 && ftell(csv) == 0) {
        unsigned short BOM = 0xfeff;
        fwrite(&BOM, 2, 1, csv);
      }
      dsint row, column;
      QString text;
      QString cell;
      if (headers.size() > 0) {
        for (column = 0; column < columns; column++) {
          cell = headers.at(column);
          if (cell.contains('"') || cell.contains(',') || cell.contains('\r') ||
              cell.contains('\n')) {
            if (cell.contains('"')) cell = cell.replace("\"", "\"\"");
            cell = "\"" + cell + "\"";
          }
          if (column != columns - 1) cell = cell + ",";
          text = text + cell;
        }
        if (utf16) {
          text = text + "\r\n";
          if (!fwrite(text.utf16(), text.length(), 2, csv)) {
            fclose(csv);
            GuiAddLogMessage(
                QApplication::translate("ExportCSV", "CSV export error\n")
                    .toUtf8()
                    .constData());
            return false;
          }
        } else {
          text = text + "\n";
          QByteArray utf8;
          utf8 = text.toUtf8();
          if (!fwrite(utf8.constData(), utf8.size(), 1, csv)) {
            fclose(csv);
            GuiAddLogMessage(
                QApplication::translate("ExportCSV", "CSV export error\n")
                    .toUtf8()
                    .constData());
            return false;
          }
        }
      }
      for (row = 0; row < rows; row++) {
        text.clear();
        for (column = 0; column < columns; column++) {
          cell = getCellContent(row, column);
          if (cell.contains('"') || cell.contains(',') || cell.contains('\r') ||
              cell.contains('\n')) {
            if (cell.contains('"')) cell = cell.replace("\"", "\"\"");
            cell = "\"" + cell + "\"";
          }
          if (column != columns - 1) cell = cell + ",";
          text = text + cell;
        }
        if (utf16) {
          text = text + "\r\n";
          if (!fwrite(text.utf16(), text.length(), 2, csv)) {
            fclose(csv);
            GuiAddLogMessage(
                QApplication::translate("ExportCSV", "CSV export error\n")
                    .toUtf8()
                    .constData());
            return false;
          }
        } else {
          text = text + "\n";
          QByteArray utf8;
          utf8 = text.toUtf8();
          if (!fwrite(utf8.constData(), utf8.size(), 1, csv)) {
            fclose(csv);
            GuiAddLogMessage(
                QApplication::translate("ExportCSV", "CSV export error\n")
                    .toUtf8()
                    .constData());
            return false;
          }
        }
      }
      fclose(csv);
      GuiAddLogMessage(
          QApplication::translate("ExportCSV", "Saved CSV data at %1\n")
              .arg(browse.path)
              .toUtf8()
              .constData());
      return true;
    }
  } else
    return false;
}

static bool allowSeasons() {
#if 0
  srand(GetTickCount());
  duint setting = 0;
  return !BridgeSettingGetUint("Misc", "NoSeasons", &setting) || !setting;
#else
  return false;
#endif
}

#if 0
static bool isChristmas() {
  auto date = QDateTime::currentDateTime().date();
  return date.month() == 12 && date.day() >= 23 && date.day() <= 26;
}
#else
#define isChristmas() false
#endif

// https://www.daniweb.com/programming/software-development/threads/463261/c-easter-day-calculation
bool isEaster() {
  auto date = QDateTime::currentDateTime().date();
  int K, M, S, A, D, R, OG, SZ, OE, X = date.year();
  K = X / 100;                                   // Secular number
  M = 15 + (3 * K + 3) / 4 - (8 * K + 13) / 25;  // Secular Moon shift
  S = 2 - (3 * K + 3) / 4;                       // Secular sun shift
  A = X % 19;                                    // Moon parameter
  D = (19 * A + M) % 30;  // Seed for 1st full Moon in spring
  R = D / 29 + (D / 28 - D / 29) * (A / 11);  // Calendarian correction quantity
  OG = 21 + D - R;                            // Easter limit
  SZ = 7 - (X + X / 4 + S) % 7;               // 1st sunday in March
  OE = 7 - (OG - SZ) % 7;  // Distance Easter sunday from Easter limit in days
  int MM = ((OG + OE) > 31) ? 4 : 3;
  int DD = (((OG + OE) % 31) == 0) ? 31 : ((OG + OE) % 31);
  return date.month() == MM && date.day() >= DD - 2 && date.day() <= DD + 1;
}

QString couldItBeSeasonal(QString icon) {
  static bool seasons = allowSeasons();
  static bool christmas = isChristmas();
  static bool easter = isEaster();
  if (!seasons) return icon;
  if (christmas)
    return QString("christmas%1.png").arg(rand() % 8 + 1);
  else if (easter)
    return QString("easter%1.png").arg(rand() % 8 + 1);
  return icon;
}
